package com.jielihaofeng.shop.security.security;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.jielihaofeng.shop.security.config.bean.SecurityProperties;
import com.jielihaofeng.shop.util.RedisUtils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Johnnie Wind
 * @description token提供者
 * @date 2021/2/26 10:51
 */
@Slf4j
@Component
public class TokenProvider implements InitializingBean {

    private SecurityProperties properties;
    private RedisUtils redisUtils;
    private static final String AUTHORITIES_KEY = "auth";
    private JwtParser jwtParser;
    private JwtBuilder jwtBuilder;

    public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
        this.properties = properties;
    }

    // InitializingBean接口的方法，当类实例被初始化时就会走该方法
    // 另外还有DisposableBean接口可实现，当类实例被销毁后调用
    @Override
    public void afterPropertiesSet() {
        byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
        Key key = Keys.hmacShaKeyFor(keyBytes);
        jwtParser = Jwts.parserBuilder()
                .setSigningKey(key)
                .build();
        jwtBuilder = Jwts.builder()
                .signWith(key, SignatureAlgorithm.HS512);
    }

    /**
     * @return java.lang.String
     * @description 创建Token 设置永不过期，Token有效期转到Redis维护
     * @author Johnnie Wind
     * @date 2021/2/26 11:12
     * @Param [claims]
     */
    public String createToken(Authentication authentication) {
        /** 获取权限列表 **/
        String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        return jwtBuilder
                .setId(IdUtil.simpleUUID()) // 设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
                .claim(AUTHORITIES_KEY, authorities) // 如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setSubject(authentication.getName())
//                .setIssuedAt(new Date()) // 签发时间
//                .setExpiration(new Date(new Date().getTime() + jwtSecurityProperties.getTokenValidityInSeconds())) // 失效时间
//                .compressWith(CompressionCodecs.DEFLATE) // 压缩方式
//                .signWith(key, SignatureAlgorithm.HS512) // 签名算法
                .compact();
    }

    /**
     * @return org.springframework.security.core.Authentication
     * @description 获取认证结果
     * @author Johnnie Wind
     * @date 2021/2/26 11:13
     * @Param [token]
     */
    public Authentication getAuthentication(String token) {
        Claims claims = getClaims(token);

        Object authoritiesStr = claims.get(AUTHORITIES_KEY);
        Collection<? extends GrantedAuthority> authorities =
                ObjectUtil.isNotEmpty(authoritiesStr) ?
                        Arrays.stream(authoritiesStr.toString().split(","))
                                .map(SimpleGrantedAuthority::new)
                                .collect(Collectors.toList())
                        : Collections.emptyList();

        User principal = new User(claims.getSubject(), "******", authorities);
        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }

    /**
     * @return io.jsonwebtoken.Claims
     * @description 获取Token中的Claims
     * @author Johnnie Wind
     * @date 2021/2/26 11:12
     * @Param [token]
     */
    private Claims getClaims(String token) {
        Claims claims;
        try {
            claims = jwtParser
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
            log.error("获取Token中的Claims错误：", e.getMessage());
            e.printStackTrace();
        }
        return claims;
    }

    /**
     * @return void
     * @description 检查是否刷新token
     * @author Johnnie Wind
     * @date 2021/2/26 16:53
     * @Param [token]
     */
    public void checkRenewal(String token) {
        // 判断是否续期token，计算token的过期时间
        long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
        DateTime expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
        // 判断当前时间与过期时间的时间差
        long differ = expireDate.getTime() - System.currentTimeMillis();
        // 如果在检查范围内，则可续期
        if (differ < properties.getDetect()) {
            Long renew = time + properties.getRenew();
            redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
        }
    }

    /**
     * @description 从请求头中获取token，去除Bearer 前缀（7个字符）
     * @author Johnnie Wind
     * @date 2021/2/26 17:01
     * @Param [request]
     * @return java.lang.String
     */
    public String getToken(HttpServletRequest request) {
        final String requestHeader = request.getHeader(properties.getHeader());
        if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
            return requestHeader.substring(7);
        }
        return null;
    }


    public boolean validateToken(String authToken) {
        try {
            jwtParser.parseClaimsJws(authToken);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            log.info("Invalid JWT signature.");
            e.printStackTrace();
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT token.");
            e.printStackTrace();
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT token.");
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            log.info("JWT token compact of handler are invalid.");
            e.printStackTrace();
        }
        return false;
    }


    /**
     * @return java.util.Date
     * @description 获取Token中的失效时间
     * @author Johnnie Wind
     * @date 2021/2/26 11:07
     * @Param [token]
     */
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaims(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
            log.error("获取Token中的失效时间错误：", e.getMessage());
            e.printStackTrace();
        }
        return expiration;
    }


}
